﻿/*
Copyright © 2011 François Karman

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.

See <license.txt> for the complete license of the software.
*/

using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using Microsoft.Phone.Reactive;
using NUnit.Framework.Api;

namespace NUnit.Phone.Gui
{
    /// <summary>
    /// Defines the view model associated with a test execution.
    /// </summary>
    /// <remarks>
    /// This class is provided as a singleton to ensure that
    /// the instance is unique and always connected to the test runner.
    /// Without this, the provided data could be wrong.
    /// </remarks>
    public class ResultListViewModel : AbstractViewModel
    {
        /// <summary>
        /// The unique instance of the class.
        /// </summary>
        private static ResultListViewModel instance;

        /// <summary>
        /// The associated test runner.
        /// </summary>
        private readonly TestRunner runner;

        /// <summary>
        /// The traces generated by the execution.
        /// </summary>
        private readonly ICollection<TestOutput> traces;

        /// <summary>
        /// The current test.
        /// </summary>
        private ITest current;

        /// <summary>
        /// The count of available tests.
        /// </summary>
        private int totalCount;

        /// <summary>
        /// The count of executed tests.
        /// </summary>
        private int executedCount;

        /// <summary>
        /// The passed tests.
        /// </summary>
        private readonly ICollection<ITestResult> passedTests;

        /// <summary>
        /// The failed tests.
        /// </summary>
        private readonly ICollection<ITestResult> failedTests;

        /// <summary>
        /// The inconclusive tests.
        /// </summary>
        private readonly ICollection<ITestResult> inconclusiveTests;

        /// <summary>
        /// The skipped tests.
        /// </summary>
        private readonly ICollection<ITestResult> skippedTests;

        /// <summary>
        /// Prevents a new instance of the ResultListViewModel class
        /// from being created.
        /// </summary>
        private ResultListViewModel(TestRunner runner)
        {
            this.traces = new ObservableCollection<TestOutput>();
            this.passedTests = new ObservableCollection<ITestResult>();
            this.failedTests = new ObservableCollection<ITestResult>();
            this.inconclusiveTests = new ObservableCollection<ITestResult>();
            this.skippedTests = new ObservableCollection<ITestResult>();

            this.runner = runner;
            this.TotalCount = this.runner.Test.TestCaseCount;

            this.runner.TestFinished += this.OnTestFinished;
            this.runner.TestStarted += this.OnTestStarted;
            this.runner.TestOutput += this.OnTestOutput;
        }

        /// <summary>
        /// Gets the unique instance of the class linked to the
        /// unique runner of the <see cref="TestManager"/>.
        /// </summary>
        public static ResultListViewModel Instance
        {
            get
            {
                if (instance == null)
                {
                    instance = new ResultListViewModel(TestManager.Instance.Runner);
                }

                return instance;
            }
        }

        /// <summary>
        /// Gets the traces generated by the execution.
        /// </summary>
        public ICollection<TestOutput> Traces
        {
            get { return this.traces; }
        }

        /// <summary>
        /// Gets the passed tests.
        /// </summary>
        public ICollection<ITestResult> PassedTests
        {
            get { return this.passedTests; }
        }

        /// <summary>
        /// Gets the failed tests.
        /// </summary>
        public ICollection<ITestResult> FailedTests
        {
            get { return this.failedTests; }
        }

        /// <summary>
        /// Gets the inconclusive tests.
        /// </summary>
        public ICollection<ITestResult> InconclusiveTests
        {
            get { return this.inconclusiveTests; }
        }

        /// <summary>
        /// Gets the skipped tests.
        /// </summary>
        public ICollection<ITestResult> SkippedTests
        {
            get { return this.skippedTests; }
        }

        /// <summary>
        /// Gets the current test.
        /// </summary>
        public ITest Current
        {
            get { return this.current; }
            private set
            {
                if (this.current != value)
                {
                    this.current = value;
                    this.RaisePropertyChanged("Current");
                }
            }
        }

        /// <summary>
        /// Gets the count of available tests.
        /// </summary>
        public int TotalCount
        {
            get { return this.totalCount; }
            private set
            {
                if (this.totalCount != value)
                {
                    this.totalCount = value;
                    this.RaisePropertyChanged("TotalCount");
                }
            }
        }

        /// <summary>
        /// Gets the count of executed tests.
        /// </summary>
        public int ExecutedCount
        {
            get { return this.executedCount; }
            private set
            {
                if (this.executedCount != value)
                {
                    this.executedCount = value;
                    this.RaisePropertyChanged("ExecutedCount");
                }
            }
        }

        /// <summary>
        /// Starts the associated test runner.
        /// </summary>
        public void ExecuteStart()
        {
            this.runner.Start();
        }

        /// <summary>
        /// Called when a test is started - updates the current test.
        /// </summary>
        /// <param name="sender">The parameter is not used.</param>
        /// <param name="e">The details of the event.</param>
        private void OnTestStarted(object sender, TestStartedEventArgs e)
        {
            this.Current = e.Test;
        }

        /// <summary>
        /// Called when a test is ended - updates the results.
        /// </summary>
        /// <param name="sender">The parameter is not used.</param>
        /// <param name="e">The details of the event.</param>
        private void OnTestFinished(object sender, TestFinishedEventArgs e)
        {
            Debug.WriteLine("OnTestFinished() [FullName:{0}] [AssertCount:{1}] [FailCount:{2}] [InconclusiveCount:{3}] [PassCount:{4}] [SkipCount:{5}]"
                , e.Result.FullName
                , e.Result.AssertCount
                , e.Result.FailCount
                , e.Result.InconclusiveCount
                , e.Result.PassCount
                , e.Result.SkipCount);

            // Clean the current test
            this.Current = null;

            // The test results are managed only if the test is a leaf
            // this way the TestCaseCount and the ExecutedTest will match
            // at the end !
            if (!e.Result.HasChildren)
            {
                // expectation: a leaf test count as 1
                Observable.ToAsync(() =>
                {
                    this.ExecutedCount++;
                    switch (e.Result.ResultState.Status)
                    {
                        case TestStatus.Passed:
                            this.PassedTests.Add(e.Result);
                            break;
                        case TestStatus.Failed:
                            this.FailedTests.Add(e.Result);
                            break;
                        case TestStatus.Inconclusive:
                            this.InconclusiveTests.Add(e.Result);
                            break;
                        case TestStatus.Skipped:
                            this.SkippedTests.Add(e.Result);
                            break;
                    }
                }, Scheduler.Dispatcher).Invoke();
            }
        }

        /// <summary>
        /// Called when the test execution generates a trace.
        /// </summary>
        /// <param name="sender">The parameter is not used.</param>
        /// <param name="e">The details of the event.</param>
        private void OnTestOutput(object sender, TestOutputEventArgs e)
        {
            this.Traces.Add(e.Output);
        }
    }
}
